Smart Lock

Angela Zou (az292), Stefen Pegels (sgp62)


Demonstration Video


Introduction

To create the smart lock system, we connected multiple peripherals to various Raspberry Pi 4 GPIO and communication interfaces in combination with a software controlled solenoid lock to open a safe door. After thorough testing of these components and weaving them together in a coherent software routine that prompted for user enrollment, authentication, and other modifications, we created a physical housing with laser cut wood to place our components for user interaction. This included creating a touch screen piTFT interface for users to select different functions, and to print output confirming specific authorization procedures. The final safe was able to enroll new users into its database, prompting them for a user ID, fingerprint, RFID tag, and password. Users with elevated permissions were able to delete others from the database.


Generic placeholder image

Project Objective:

    This project attempted to create a smart safe system that utilizes fingerprint sensing, RFID tag input, and password input from a keypad to authenticate and store authorized users in a database held on the Raspberry Pi 4. A complementary touch screen interface would interact with users as they attempt to unlock the safe.


Design and Testing

Hardware Design

Designing the hardware for the smart lock system first involved setting up all of the pin connections between the Raspberry Pi 4 and the sensors used. These included a UART fingerprint sensor, a UART RFID sensor (optionally SPI), a 3x4 Matrix keypad (7 GPIOs), the solenoid lock (one GPIO) and the piTFT with its associated reserved GPIOs. These logical connections can be observed in the following diagram:

Hardware Connection Diagram

To enable an additional hardware UART port (UART3 for the RFID sensor), the following command was run: dtoverlay=uart3. Afterwards, the additional UART port was discoverable as /dev/ttyAMA1 in addition to /dev/ttyS0 used by the fingerprint sensor. The UART console for ttyS0 was disabled in order to use that hardware serial port. Data from these sensors, as well as the GPIO inputs for the matrix keypad, were read in and used during the software routines, as discussed in the Software section.

The matrix keypad was used to gather input numbers for user passwords and IDs, and used 7 GPIOs in a row by column format for the numbers of the keypad. These GPIOs were used to avoid those utilized by the piTFT. One final GPIO was used to open the safe itself, which was connected to a power relay for the solenoid lock. This relay was attached to a 6V power supply which drew ~1A of current when the lock was opened.

All of the components were connected to the Pi 4 on a breadboard, and each component was tested individually before combining one after the other. We had initial problems setting up the extra UART port, as well as assigning GPIOs for the matrix keypad already used by the piTFT. One part of the design that interfaced very smoothly was the RFID sensor; it amazingly worked on the first try. The relay for the solenoid lock had some difficulties due to incorrect pin assignment which were fixed quickly.

Front Panel

To create the safe door, the laser cut wood was reattached with two hinges, and the solenoid lock was screwed on from the inside. A small tab of wood was added to prevent the door from being pushed inwards, since it opens outwards once the lock is open.

Inside

The angle of the solenoid lock prevents the door from being pulled outwards. Also visible is our top secret money storage hidden inside! The components were taped to their laser cut outlines on the front panel to fix them in place. An industrial safe with this kind of sophisticated authentication would most likely have a more secure physical system. This physical system was tested with just the front panel to ensure no unplugged jumper cables before fastening it to the rest of the box. Jumper cables were fastened with electrical tape to avoid accidental wire disconnections. Foam was added to cushion the electronics and shield the screws used for the hinges.

Software Design

Our main software program starts by including all of the libraries, setting up the GPIO port for the lock, keypad, and PiTFT buttons for external control, and the UART ports for the fingerprint sensor and RFID sensor. The main program loop stays in a while loop and the only exit condition is pressing the piTFT button 27. Each while loop iteration firstly displays the four buttons and waits on user input. This is done with the PyGame library. Four text boxes are created with their bounding boxes highlighted. The program then waits for the users to click on the PiTFT screen and checks the touch coordinate to see which button gets pressed.

Start Up Button

After the button press is detected, the program would enter the corresponding branch segment. There are four options the user can choose: enrolling a new user, performing authentication and opening the safebox, adding fingerprint to an existing user, and deleting users in the system as an admin.

To enroll into the system, the users need to enter their user IDs, passwords, user permission levels, RFIDs if an admin user, and then their fingerprints. This allows multiple levels of authentication to ensure the safety of access. Both the user ID and password are entered through the matrix keypad. The user ID is checked with the system database to ensure there is no duplication. The user will be prompted to enter the password twice to ensure they match. If the user permission level is set to 1, the user will be marked as an admin/superuser and thus will be prompted to scan the RFID card. The fingerprint sensor allows users to enroll their fingerprints with the adafruit fingerprint sensor library. The enroll function scans the fingerprint twice, creates feature vectors, and stores the data into the non-volatile ROM on the sensor. For each step a failure tolerance mechanism is used so that the user can enter wrong information up to 5 times before the program exit and restart from the top of the while loop. Once all of the information gets entered correctly, they are stored as a dictionary element with user ID as the key in the system database – which is a JSON file. To fully utilize the PiTFT functionality, we have a text box displayed at the bottom of the screen prompting the user what to do for the next step. The length of the sentence to display is analyzed and parsed into different PyGame text surfaces to make sure it fits into the text box boundary.

User Interface Text Box

To perform the authentication, the user is prompted to enter the User ID, and the program will check the database and obtain the information corresponding to the ID. Then the user will enter the password through the matrix keyboard, scan the RFID if a superuser, and enter the fingerprint through the sensor. If all of the information entered matches with the record, the program will pull the lock pin high for 10 seconds, which unlocks the safe and the user can open the door and see the prize. To enter an extra fingerprint, the user firstly goes through the same authentication process, and if recognized as a valid user, the user has the opportunity to enter a new fingerprint. The sensor library returns a storage location in the sensor ROM and the value will be appended to the fingerprint ID field of the user record. Then the next time the user can scan either finger to open the lock. The delete user feature is specific for superusers that have the permission to modify the system. After authenticating themselves and proving they are valid superusers with the high level permission, the system prompts all existing user IDs in the database and the admins can enter the user ID to delete. This will not only delete the user record but also their fingerprint on the fingerprint sensor so that the storage space can be reallocated for new users.


Testing

We took an incremental approach to our project. We started off by connecting the peripheral one by one, implementing standalone programs to ensure each part is working separately. For instance, the matrix keypad test prints every single press on the keypad so that we can understand how to set the delay to minimize glitches. The fingerprint sensor is stress tested with the library function call to ensure it enrolls and authenticates fingerprint properly. Then we gradually added the hardwares into the main program, firstly running simple steps to ensure there are no GPIO conflicts among the peripherals. To make debugging easier, we firstly used the terminal output to print the program execution flow, after ensuring all steps worked as expected, we migrated the user interaction interface to the PiTFT screen. The last step is the physical construction of the smart lock safebox. By incrementally connecting the hardwares to the Raspberry Pi and runs the standalone test programs to make sure the connection is correct, we successfully migrated our smart lock onto the cardboard box.


Result

The system works well with all implemented features. We are able to add new users, perform authentications for both normal and super users, delete user records, and add multiple fingerprints to the system. The mechanical and physical construction also works perfectly.


Conclusion

Our project achieved its desired function of a working safe that only opened once a registered user was authenticated. The sensors were reliable, especially the RFID sensor and touch screen. Sometimes fingerprints did not register properly for some individuals or specific fingers, and some keypad inputs required multiple presses to register. These did not detract from the overall system robustness. Superuser authentication to delete users also worked well, as did enrolling multiple fingerprints. An initial idea involved using multiple fingerprints to have simultaneous inputs for authentication, but we utilized nearly all available GPIO pins for the current system and are impressed with its current setup. We initially tried to add a hardware reset button to go back to the main authentication screen, but this did not work properly when the authentication script was run inside .bashrc. Overall, we are very satisfied with how the smart lock system came together, especially the laser cut for the wood on the front panel and the stability of the electrical system.


Future Work

Potential future work mainly consists of two parts: implementing more features to the system and improving the user interface. Some potential features that can be added include allowing users to update password, incorporating gesture password onto the PiTFT screen, simultaneous fingerprint authentication for higher level permission, and writing user records to RFIDs to allow simplified authentication. Examples of potential interface improvements are scrolling text display and multiple levels of buttons to allow extended features.


Work Distribution

Generic placeholder image

Angela Zou

az292@cornell.edu

Designed the overall software architecture.

Generic placeholder image

Stefen Pegels

sgp62@cornell.edu

Designed the electrical system and performed physical construction.


Parts List

Total: $78.37


Code Appendix

display_lib.py -- used for display on PiTFT


        # --------------------------------------------------------------------------
        # display_lib.py -- used for display on PiTFT
        # --------------------------------------------------------------------------
         
        import pygame
        import time
        import os
        from pygame.locals import *
         
        os.putenv('SDL_VIDEODRIVER', 'fbcon')
        os.putenv('SDL_FBDEV', '/dev/fb0')
        os.putenv('SDL_MOUSEDRV', 'TSLIB')
        os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
         
        pygame.init()
        pygame.display.init()
        pygame.mouse.set_visible(False)
        WHITE = 255,255,255
        BLACK = 0,0,0
        screen = pygame.display.set_mode((320, 240))
        my_font = pygame.font.Font(None, 20)
        text_rect = pygame.Rect(0, 120, 318, 118)
        screen.fill(BLACK)
         
        level1_buttons = {"New User":(20,20), "Authentication":(180,20), "Add Fingerprint":(20,70), "Delete User":(180,70)}
        button_width = 120
        button_height = 30
         
        def display_buttons(level_buttons, text=""):
         screen.fill(BLACK)
         for button_text,rec_pos in level_buttons.items():
           button_rect = pygame.Rect(rec_pos[0], rec_pos[1], button_width, button_height)
           pygame.draw.rect(screen, WHITE, button_rect, 2)
           text_surface = my_font.render(button_text, True, WHITE)
           text_center = (rec_pos[0]+button_width/2, rec_pos[1]+button_height/2)
           screen.blit(text_surface, text_surface.get_rect(center=text_center))
         pygame.draw.rect(screen, WHITE, text_rect, 2)
         pygame.display.flip()
         
        def check_level1_button():
         while True:
           for event in pygame.event.get():
             if (event.type is MOUSEBUTTONDOWN):
               pos = pygame.mouse.get_pos()
             elif (event.type is MOUSEBUTTONUP):
               pos = pygame.mouse.get_pos()
               x,y = pos
               if (x>20 and x<140 and y>20 and y<50):
                 return 1
               elif (x>180 and x<300 and y>20 and y<50):
                 return 2
               elif (x>20 and x<140 and y>70 and y<100):
                 return 3
               elif (x>180 and x<300 and y>70 and y<100):
                 return 4
         
        def renderTextCenteredAt(text, font, colour, x, y, screen, allowed_width):
         words = text.split()
         lines = []
         while len(words) > 0:
           line_words = []
           while len(words) > 0:
             line_words.append(words.pop(0))
             fw, fh = font.size(' '.join(line_words + words[:1]))
             if fw > allowed_width:
               break
           line = ' '.join(line_words)
           lines.append(line)
         y_offset = 0
         for line in lines:
           fw, fh = font.size(line)
           tx = x
           ty = y + y_offset
           font_surface = font.render(line, True, colour)
           screen.blit(font_surface, (tx, ty))
           y_offset += fh
         
        def display_text(my_text):
           screen.fill(BLACK)
           pygame.draw.rect(screen, WHITE, text_rect, 2)
           renderTextCenteredAt(my_text, my_font, WHITE, 5, 125, screen, 310)
           pygame.display.flip()
           time.sleep(0.5)
         
          

fingerprint_lib.py -- used for fingerprint sensor

 
        # --------------------------------------------------------------------------
        # fingerprint_lib.py -- used for fingerprint sensor
        # --------------------------------------------------------------------------
        import time
        import adafruit_fingerprint
        from display_lib import *
         
        def get_fingerprint(finger):
           """Get a finger print image, template it, and see if it matches!"""
           text2display = "Waiting for image . . . "
           display_text(text2display)
           while finger.get_image() != adafruit_fingerprint.OK:
               pass
           text2display += "Templating . . . "
           display_text(text2display)
           if finger.image_2_tz(1) != adafruit_fingerprint.OK:
               return False
           text2display += "Searching . . . "
           display_text(text2display)
           if finger.finger_search() != adafruit_fingerprint.OK:
               return False
           return True
         
         
        # pylint: disable=too-many-branches
        def get_fingerprint_detail(finger):
           """Get a finger print image, template it, and see if it matches!
           This time, print out each error instead of just returning on failure"""
           print("Getting image...", end="")
           i = finger.get_image()
           if i == adafruit_fingerprint.OK:
               print("Image taken")
           else:
               if i == adafruit_fingerprint.NOFINGER:
                   print("No finger detected")
               elif i == adafruit_fingerprint.IMAGEFAIL:
                   print("Imaging error")
               else:
                   print("Other error")
               return False
         
           print("Templating...", end="")
           i = finger.image_2_tz(1)
           if i == adafruit_fingerprint.OK:
               print("Templated")
           else:
               if i == adafruit_fingerprint.IMAGEMESS:
                   print("Image too messy")
               elif i == adafruit_fingerprint.FEATUREFAIL:
                   print("Could not identify features")
               elif i == adafruit_fingerprint.INVALIDIMAGE:
                   print("Image invalid")
               else:
                   print("Other error")
               return False
         
           print("Searching...", end="")
           i = finger.finger_fast_search()
           # pylint: disable=no-else-return
           # This block needs to be refactored when it can be tested.
           if i == adafruit_fingerprint.OK:
               print("Found fingerprint!")
               return True
           else:
               if i == adafruit_fingerprint.NOTFOUND:
                   print("No match found")
               else:
                   print("Other error")
               return False
         
         
        # pylint: disable=too-many-statements
        def enroll_finger(finger, location):
           """Take a 2 finger images and template it, then store in 'location'"""
           for fingerimg in range(1, 3):
               text2display = ""
               if fingerimg == 1:
                   text2display += "Place finger on sensor: "
                   display_text(text2display)
               else:
                   text2display += "Place same finger again: "
                   display_text(text2display)
         
               while True:
                   i = finger.get_image()
                   if i == adafruit_fingerprint.OK:
                       text2display += "Image taken"
                       display_text(text2display)
                       break
                   elif i == adafruit_fingerprint.NOFINGER:
                       text2display += ". "
                       display_text(text2display)
                   elif i == adafruit_fingerprint.IMAGEFAIL:
                       text2display += "Imaging error"
                       display_text(text2display)
                       return False
                   else:
                       text2display += "Other error"
                       display_text(text2display)
                       return False
         
               text2display = "Templating. . .  "
               display_text(text2display)
               i = finger.image_2_tz(fingerimg)
               if i == adafruit_fingerprint.OK:
                   text2display += "Templated"
                   display_text(text2display)
               else:
                   if i == adafruit_fingerprint.IMAGEMESS:
                       text2display += "Image too messy"
                       display_text(text2display)
                   elif i == adafruit_fingerprint.FEATUREFAIL:
                       text2display += "Could not identify features"
                       display_text(text2display)
                   elif i == adafruit_fingerprint.INVALIDIMAGE:
                       text2display += "Image invalid"
                       display_text(text2display)
                   else:
                       text2display += "Other error"
                       display_text(text2display)
                   return False
         
               if fingerimg == 1:
                   display_text("Remove finger")
                   time.sleep(1)
                   while i != adafruit_fingerprint.NOFINGER:
                       i = finger.get_image()
         
           text2display = "Creating model . . .  "
           display_text(text2display)
           i = finger.create_model()
           if i == adafruit_fingerprint.OK:
               text2display += "Created"
               display_text(text2display)
           else:
               if i == adafruit_fingerprint.ENROLLMISMATCH:
                   text2display += "Prints did not match"
                   display_text(text2display)
               else:
                   text2display += "Other error"
                   display_text(text2display)
               return False
         
           text2display = "Storing model #" + str(location) + " . . .  "
           display_text(text2display)
           i = finger.store_model(location)
           if i == adafruit_fingerprint.OK:
               text2display += "Stored"
               display_text(text2display)
           else:
               if i == adafruit_fingerprint.BADLOCATION:
                   text2display += "Bad storage location"
                   display_text(text2display)
               elif i == adafruit_fingerprint.FLASHERR:
                   text2display += "Flash storage error"
                   display_text(text2display)
               else:
                   text2display += "Other error"
                   display_text(text2display)
               return False
         
           return True
         
         
        def save_fingerprint_image(finger, filename):
           """Scan fingerprint then save image to filename."""
           while finger.get_image():
               pass
         
           # let PIL take care of the image headers and file structure
           from PIL import Image  # pylint: disable=import-outside-toplevel
         
           img = Image.new("L", (256, 288), "white")
           pixeldata = img.load()
           mask = 0b00001111
           result = finger.get_fpdata(sensorbuffer="image")
         
           # this block "unpacks" the data received from the fingerprint
           #   module then copies the image data to the image placeholder "img"
           #   pixel by pixel.  please refer to section 4.2.1 of the manual for
           #   more details.  thanks to Bastian Raschke and Danylo Esterman.
           # pylint: disable=invalid-name
           x = 0
           # pylint: disable=invalid-name
           y = 0
           # pylint: disable=consider-using-enumerate
           for i in range(len(result)):
               pixeldata[x, y] = (int(result[i]) >> 4) * 17
               x += 1
               pixeldata[x, y] = (int(result[i]) & mask) * 17
               if x == 255:
                   x = 0
                   y += 1
               else:
                   x += 1
         
           if not img.save(filename):
               return True
           return False
         
        def new_fingerprint_location(finger):
         """Find the next empty fingerprint location"""
         read_count = 0
         while finger.read_templates() != adafruit_fingerprint.OK:
           read_count += 1
           if (read_count == 10):
             raise RuntimeError("Failed to read templates")
         new_loc = len(finger.templates)
         if new_loc < 299:
           return new_loc
         else:
           return -1
         
         
          

authentication.py -- used for the smart lock authentication system

 
        # --------------------------------------------------------------------------
        # authentication.py -- used for the smart lock authentication system
        # --------------------------------------------------------------------------
        import time
        import serial
        import digitalio
        import RPi.GPIO as GPIO
        import board
        import json
        import adafruit_fingerprint
        import adafruit_matrixkeypad
        from adafruit_pn532.uart import PN532_UART
        import pygame
        import os
        import sys
        from pygame.locals import *
        from fingerprint_lib import *
        from display_lib import *
         
        # PIN UTILIZATION
        # Keypad: C2-22 R1-6 C1-13 R4-19 C3-26 R3-20 R2-21
        # Fingerprint Sensor: /dev/ttyS0 GPIO14 GPIO15
        # RFID: /dev/ttyACM1 GPIO4 GPIO5
        # LOCK: GPIO17
         
        # lock setup
        GPIO.setmode(GPIO.BCM)
        lock_pin = 17
        GPIO.setup(lock_pin, GPIO.OUT)
        # fingerprint setup
        uart = serial.Serial("/dev/ttyS0", baudrate=57600, timeout=1)
        finger = adafruit_fingerprint.Adafruit_Fingerprint(uart)
        if finger.read_templates() != adafruit_fingerprint.OK:
           raise RuntimeError("Failed to read templates")
        if finger.count_templates() != adafruit_fingerprint.OK:
           raise RuntimeError("Failed to read templates")
        if finger.read_sysparam() != adafruit_fingerprint.OK:
           raise RuntimeError("Failed to get system parameters")
        # keypad setup
        cols = [digitalio.DigitalInOut(x) for x in (board.D13, board.D22, board.D26)]
        rows = [digitalio.DigitalInOut(x) for x in (board.D6, board.D21, board.D20, board.D19)]
        keys = ((1, 2, 3), (4, 5, 6), (7, 8, 9), ("*", 0, "#"))
        keypad = adafruit_matrixkeypad.Matrix_Keypad(rows, cols, keys)
        # rfid setup
        uart = serial.Serial("/dev/ttyAMA1", baudrate=115200, timeout=1)
        pn532 = PN532_UART(uart, debug=False)
        ic, ver, rev, support = pn532.firmware_version
        # Configure PN532 to communicate with MiFare cards
        pn532.SAM_configuration()
         
        GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        def GPIO27_callback(channel):
           pygame.quit()
           quit()  
        GPIO.add_event_detect(27, GPIO.FALLING, callback=GPIO27_callback)
         
        GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        def GPIO23_callback(channel):
         os.execl(sys.executable, os.path.abspath(__file__), *sys.argv)
        GPIO.add_event_detect(23, GPIO.FALLING, callback=GPIO23_callback)
         
        def enter_userid(dictionary):
         valid_id = False
         while (not valid_id):
           text2display = "Enter User ID on Keyboard end with #: "
           display_text(text2display)
           id = ""
           while True:
             time.sleep(0.15)
             keys = keypad.pressed_keys
             if (keys):
               if (str(keys[0])=="#"):
                 break
               id+=str(keys[0])
               text2display += str(keys[0])
               display_text(text2display)
           if (len(id)==0):
             display_text("ID cannot be empty, please retry")
           elif (id in dictionary.keys()):
             display_text("Duplicated ID in the system, please enter a new ID")
           else:
             valid_id = True
         return id
         
        def enter_userid_superuser(dictionary):
         valid_id = False
         while (not valid_id):
           text2display = "Enter User ID to Delete on Keyboard end with #: "
           display_text(text2display)
           id = ""
           while True:
             time.sleep(0.15)
             keys = keypad.pressed_keys
             if (keys):
               if (str(keys[0])=="#"):
                 break
               id+=str(keys[0])
               text2display += str(keys[0])
               display_text(text2display)
           if (len(id)==0):
             display_text("ID cannot be empty, please retry")
           elif (id not in dictionary.keys()):
             display_text("ID not in the system, please enter a valid ID")
           else:
             valid_id = True
         return id
         
        def check_userid(dictionary):
         valid_id = False
         while (not valid_id):
           text2display = "Enter User ID on Keyboard end with #: "
           display_text(text2display)
           id = ""
           while True:
             time.sleep(0.15)
             keys = keypad.pressed_keys
             if (keys):
               if (str(keys[0])=="#"):
                 break
               id+=str(keys[0])
               text2display += str(keys[0])
               display_text(text2display)
           if (len(id)==0):
             display_text("ID cannot be empty, please retry")
           elif (not (id in dictionary.keys())):
             display_text("User ID cannot be found, please re-enter user ID")
           else:
             user = dictionary[id]
             valid_id = True
         return user
         
        def enter_password():
         password_match = False
         error_count = 0
         while (not password_match and error_count < 5):
           text2display = "Enter 4-digit Password on Keyboard: "
           display_text(text2display)
           key_count = 0
           password = ""
           while key_count < 4:
             keys = keypad.pressed_keys
             if (keys):
               password+=str(keys[0])
               key_count += 1
               text2display += " *"
               display_text(text2display)
             time.sleep(0.15)
           text2display = "Re-Enter 4-digit Password on Keyboard: "
           display_text(text2display)
           key_count = 0
           while key_count < 4:
             keys = keypad.pressed_keys
             if (keys):
               if (password[key_count]==str(keys[0])):
                 key_count += 1
                 text2display += " *"
                 display_text(text2display)
                 if (key_count==4):
                   display_text("Password Match, Save to System")
                   password_match = True
               else:
                 display_text("Password Mismatch")
                 key_count = 4
                 error_count += 1
                 if (error_count == 5):
                   display_text("Too many failed attempts, please restart")
                   return -1
             time.sleep(0.15)
         return password
         
        def check_password(user):
         for i in range(5):
           text2display = "Enter 4-digit Password on Keyboard: "
           display_text(text2display)
           key_count = 0
           password = ""
           while key_count < 4:
             keys = keypad.pressed_keys
             if (keys):
               password+=str(keys[0])
               key_count += 1
               text2display += " *"
               display_text(text2display)
             time.sleep(0.15)
           if (password==user["password"]):
             display_text("Correct password")
             return True
           else:
             display_text("Wrong password, please retry")
         display_text("Too many failed attempts, exiting ...")
         return False
         
        def enter_permission():
         for i in range(5):
           text2display = "Enter Permission Level (0 for low, 1 for high): "
           display_text(text2display)
           key_count = 0
           permission = ""
           while key_count < 1:
             time.sleep(0.15)
             keys = keypad.pressed_keys
             if (keys):
               permission+=str(keys[0])
               key_count += 1
               text2display += permission
               display_text(text2display)
           if (permission=="0"):
             display_text("Permission level: low, no RFID authentication needed")
             return permission
           elif (permission=="1"):
             display_text("Permission level: high, RFID authentication needed")
             return permission
           else:
             display_text("Invalid permission level, please re-enter")
         display_text("Too many failures, please restart")
         return "-1"
         
        def enter_rfid():
         text2display = "Waiting for RFID/NFC card: "
         display_text(text2display)
         timeout = 0
         while (timeout < 20):
           text2display+= ". "
           display_text(text2display)
           uid = pn532.read_passive_target(timeout=0.5)
           timeout += 1
           if uid is None:
             continue
           text2display+= "  RFID card found"
           display_text(text2display)
           return int.from_bytes(uid, "big")
         display_text(" RFID enrollment timed out, please restart")
         return False
         
        def check_rfid(user):
         error = 0
         timeout = 0
         text2display = "Waiting for RFID/NFC card: "
         display_text(text2display)
         while (error < 5) and (timeout < 20):
           text2display += ". "
           display_text(text2display)
           uid = pn532.read_passive_target(timeout=0.5)
           timeout += 1
           if uid is None:
             continue
           rfid = int.from_bytes(uid, "big")
           if (rfid == user["rfid"]):
             display_text("RFID authenticated")
             return True
           else:
             text2display = "RFID mismatch, please retry: "
             display_text(text2display)
             error += 1
         if (error >= 5):
           display_text("Too many failed attempts, please restart authentication")
         if (timeout >= 20):
           display_text("RFID authentication timed out, please restart")
         return False
         
        def enter_fingerprint():
           fingerprint_location = new_fingerprint_location(finger)
           if (fingerprint_location == -1):
             return -1
           else:
             get_finger = False
             while not get_finger:
               get_finger = enroll_finger(finger, fingerprint_location)
             return fingerprint_location
         
        def check_fingerprint(finger, user):
         display_text("Please enter your fingerprint")
         for i in range(5):
           res = get_fingerprint(finger)
           if (res==True):
             if (finger.finger_id in user["fingerprint"]):
               display_text("Fingerprint authenticated")
               return True
             else:
               display_text("Fingerprint not matching the record, you are not authorized")
               return False
           else:
             display_text("Fingerprint not found, please try again")
             time.sleep(2)
         return False
         
        def unlock(lock_time):
         GPIO.output(lock_pin, 1)
         time.sleep(lock_time)
         GPIO.output(lock_pin, 0)
         
        # load existing user info
        f = open("info.json")
        dictionary = json.load(f)
        quit_var = False
         
        while not quit_var:
         GPIO.output(lock_pin, 0)
         display_buttons(level1_buttons)
         # 1: new user; 2: authentication; 3: add fingerprint; 4: delete user
         c = check_level1_button()
         display_buttons(level1_buttons, str(c))
         if c==1:
           id = enter_userid(dictionary)
           permission = enter_permission()
           if (permission=="-1"):
             continue
           elif (permission=="1"):
             rfid = enter_rfid()
             if (rfid==False):
               continue
           else:
             rfid = -1
           password = enter_password()
           if (password == -1):
             continue
           fingerprint_location = enter_fingerprint()
           if (fingerprint_location == -1):
             display_text("Fingerprint storage overflow, unable to add new user. Please notify system admin.")
           else:
             dictionary[id] = {"fingerprint":[fingerprint_location],
                               "password": password,
                               "rfid": rfid,
                               "permission": permission
                             }
             display_text("User " + id + " successfully added to the system.")
             with open("info.json", "w") as outfile:
               json.dump(dictionary, outfile,indent=2)
         if c==2:
           user = check_userid(dictionary)
           fingerprint = check_fingerprint(finger, user)
           if not fingerprint:
             continue
           password = check_password(user)
           if not password:
             continue
           if (user["permission"] == "1"):
             rfid = check_rfid(user)
             if not rfid:
               continue
             else:
               unlock(2)
           else:
             unlock(2)
         if c==3:
           user = check_userid(dictionary)
           fingerprint = check_fingerprint(finger, user)
           if not fingerprint:
             continue
           password = check_password(user)
           if not password:
             continue
           if (user["permission"] == "1"):
             rfid = check_rfid(user)
             if not rfid:
               continue
           display_text("User Authenticated. You can add additional fingerprint now.")
           fingerprint_location = enter_fingerprint()
           user["fingerprint"].append(fingerprint_location)
           display_text("New fingerprint added.")
           with open("info.json", "w") as outfile:
             json.dump(dictionary, outfile,indent=2)
         if c==4:
           user = check_userid(dictionary)
           fingerprint = check_fingerprint(finger, user)
           if not fingerprint:
             continue
           password = check_password(user)
           if not password:
             continue
           if (user["permission"] == "0"):
             display_text("You don't have enough permission to manage the database")
             continue
           rfid = check_rfid(user)
           if not rfid:
             continue
           quit_superuser = 0
           if (not quit_superuser):
             print("You are allowed to delete users in the database")
             display_text(str([key for key in dictionary.keys()]))
             id = enter_userid_superuser(dictionary)
             for fingerprint in dictionary[id]["fingerprint"]:
               if finger.delete_model(fingerprint) == adafruit_fingerprint.OK:
                 display_text("Fingerprint deleted!")
               else:
                 display_text("Failed to delete fingerprint, please retry")
                 continue
             del(dictionary[id])
             with open("info.json", "w") as outfile:
               json.dump(dictionary, outfile,indent=2)
             display_text("User" + id + " is deleted!")